/*  Copyright 2011 Ya Shou, All rights reserve
**
**  This program is free software: you can redistribute
**  it and/ormodify it under the terms of the GNU General
**  Public License as published by the Free Software
**  Foundation, either version 3 of the License, or (at
**  your option) any later version.
**
**  This program is distributed in the hope that it will
**  be useful, but WITHOUT ANY WARRANTY; without even the
**  implied warranty of MERCHANTABILITY or FITNESS FOR A
**  PARTICULAR PURPOSE.  See the GNU General Public License
**  for more details.
**
**  You should have received a copy of the GNU General
**  Public License
**
**  *****************  File Info  *************************
**
**  CMCHECK -- Cmlr Memory leak CHECKer
**
**  Author: CMlr<zxyzxy12321@gmail.com>
**  Date: May 26, 2011
**  Project: cmcheck
**  Description: the main header file of cmcheck module. this
**      module is design for check memory leak problem.
**      compatible with c and c++, cross platform.
*/

#ifndef CMCHECK_H_
#define CMCHECK_H_


/* header included */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#ifndef _MSC_VER
#include <stdint.h>
#else   /* _MSC_VCR */
/* vc have not the header file */
typedef __int8 int8_t;
typedef unsigned __int8 uint8_t;
typedef __int16 int16_t;
typedef unsigned __int16 uint16_t;
typedef __int32 int32_t;
typedef unsigned __int32 uint32_t;
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#endif  /* _MSC_VCR */


#ifdef __cplusplus

/* for typeid, type_info */
#ifdef CMC_RTTI
#if defined(_MSC_VER) /* vc compiler */
#include <typeinfo.h>
#elif defined(__GNUC__) /* gcc compiler */
#include <typeinfo>
#endif
#endif  /* CMC_RTTI */

/* for bad_alloc */
#include <new>


#endif  /* __cplusplus */


/******************** begin interface ******************/

/* flag constants definition */

/* print only the memory leak */
#define CMC_PRINT_ONLY_LEAKING      0x01

/* use this flag when you need frequent
 * application and release memory, in order
 * to reduce unwanted spam
 * notice: when using this flag, you will
 *      not be able to get to the memory of the
 *      information has been released
 */
#define CMC_AUTO_RELEASE            0x02


/* export interface */
#ifdef __cplusplus
extern "C" {
#endif  /* __cplusplus */

/* cmcheck module initialization */
void cmc_init(void);

/* exit call, print the information leak and
 * clean up the cmcheck memory */
void cmc_at_exit(void);

/* release cmcheck occupied memory */
void cmc_release(void);

/* print current leak status, call at anytime */
void cmc_print_leak_status();

/* set bit flags */
void cmc_set_flags(uint32_t flag);

/* set output file */
void cmc_set_ouput_file(FILE* out_file);

/* memory allocation */
void* cmc_malloc_base(
    uint32_t size,
    const char* filename,
    uint32_t lineno
);
/* use like standard library function 'malloc' */
#define cmc_malloc(size) \
    cmc_malloc_base(size, __FILE__, __LINE__)
/* use like standard library function 'calloc' */
#define cmc_calloc(count, size) \
    cmc_malloc_base(((count) * (size)), __FILE__, __LINE__)

void* cmc_realloc_base(
    void* pointer,
    uint32_t size,
    const char* filename,
    uint32_t lineno
);
/* use like standard library function 'realloc' */
#define cmc_realloc(pointer, size) \
    cmc_malloc_base(pointer, size, __FILE__, __LINE__)

/* memory free */
void cmc_free_base(
    void* pointer
);
/* use like standard library function 'free' */
#define cmc_free(pointer) \
    cmc_free_base(pointer)


#ifdef __cplusplus
}   /* extern "C" */
#endif  /* __cplusplus */

#ifdef __cplusplus

/* new operator & new array operator */
/* new operator */
template<class Type>
void* cmc_new_base(
    const char* filename,
    uint32_t lineno
);

/* new array operator */
template<class Type>
void* cmc_new_array_base(
    uint32_t length,
    const char* filename,
    uint32_t lineno
);

/* use like new operator, just brackets more */
#define cmc_new(type) \
    static_cast<type*>(cmc_new_base<type>( \
        __FILE__, \
        __LINE__))

#define cmc_new_array(type, length) \
    static_cast<type*>(cmc_new_array_base<type>( \
        length, \
        __FILE__, \
        __LINE__))

/* delete operator & delete array operator */
template<class Type>
void cmc_delete_base(Type* pointer);

template<class Type>
void cmc_delete_array_base(Type* pointer);

#define cmc_delete(pointer) \
    cmc_delete_base(pointer);

#define cmc_delete_array(pointer) \
    cmc_delete_array_base(pointer);

#endif  /* __cplusplus */


/******************** end interface ********************/

/*               ! NOTICE !
 * the following is the realization of part of the library,
 * it is best not to directly call. Unless you are well
 * aware of what they are doing.
 */

/* local configure variables */
/* output file */
static FILE* cmc_out_file;
/* print flags */
static uint32_t cmc_flags;

/* memory block struct */
struct cmc_memblk_node_t {
    struct cmc_memblk_node_t* prev, *next;
    void* ptr;
    uint32_t size;
#ifdef CMC_RTTI
    const char* type;
#endif  /* CMC_RTTI */
    uint32_t released;
    const char* filename;
    uint32_t lineno;
};

/* memory block list operation */
static struct cmc_memblk_node_t* cmc_memblk_list_head;
static struct cmc_memblk_node_t* cmc_memblk_list_tail;
static uint32_t cmc_memblk_list_size;


/* vc functions rename */
#ifdef _MSC_VER
/* disable vc unsafe warning */
#pragma warning(push)
#pragma warning(disable: 4996)
/* snprintf in vc called _snprintf */
#define snprintf _snprintf
#endif  /* _MSC_VER */


/* initialize a list item */
void cmc_list_item_init(
    struct cmc_memblk_node_t* item);
/* append a list item to list end */
void cmc_list_append(
    struct cmc_memblk_node_t* item);
/* remove item from list */
void cmc_list_remove(
    struct cmc_memblk_node_t* item);
/* release the memory occupied of special item */
void cmc_list_release_item(
    struct cmc_memblk_node_t* item);
/* find item by its pointer */
struct cmc_memblk_node_t*
    cmc_list_find_byptr(void* ptr);


/* implemention */
void cmc_init(void) {
    cmc_out_file = stdout;
    cmc_flags = 0;

    /* initialize memory block list */
    cmc_memblk_list_head = 0;
    cmc_memblk_list_tail = 0;
    cmc_memblk_list_size = 0;
}

void cmc_at_exit(void) {
    cmc_print_leak_status();
    cmc_release();
}

void cmc_release(void) {
    while (cmc_memblk_list_head != 0) {
        cmc_list_remove(cmc_memblk_list_head);
    }
}


/* set print bit flags */
void cmc_set_flags(uint32_t flag) {
    cmc_flags = flag;
}


void cmc_print_leak_status() {
    struct cmc_memblk_node_t* item =
        cmc_memblk_list_head;

#ifdef CMC_RTTI
    static const char* headerformat =
      /* addr    size   rlzd   type    line   filename */
        "%-10s - %-4s - %-1s - %-15s - %-4s - %-28s-\n";
    static const char* formattext =
      /* addr    size   rlzd   type    line   filename */
        "0x%8p - %04d - %-1s - %-15s - %04d - %-28s-\n";

#else   /* CMC_RTTI */
    static const char* headerformat =
      /* addr    size   rlzd   line   filename */
        "%-10s - %-4s - %-1s - %-4s - %-28s-\n";
    static const char* formattext =
      /* addr    size   rlzd   line   filename */
        "0x%8p - %04d - %-1s - %04d - %-28s-\n";
#endif  /* CMC_RTTI */


    fprintf(cmc_out_file, headerformat,
        "address",
        "size",
        "R",
#ifdef CMC_RTTI
        "type",
#endif  /* CMC_RTTI */
        "line",
        "filename");

    while (item != 0) {
        if ((cmc_flags & CMC_PRINT_ONLY_LEAKING) &&
            (item->released == 1)) {
            item = item->next;
            continue;
        }
        fprintf(cmc_out_file, formattext,
            item->ptr,
            item->size,
            item->released == 1? "Y" : "N",
#ifdef CMC_RTTI
            item->type,
#endif  /* CMC_RTTI */
            item->lineno,
            item->filename);

        item = item->next;
    }
}


/* set output file */
void cmc_set_ouput_file(FILE* out_file) {
    cmc_out_file = out_file;
}


/* main implement content */

/* malloc */
void* cmc_malloc_base(
      uint32_t size,
      const char* filename,
      uint32_t lineno) {
    struct cmc_memblk_node_t* item =
        (struct cmc_memblk_node_t*)malloc(
          sizeof(struct cmc_memblk_node_t)
        );
    
    size_t filenamelength = strlen(filename) + 1;
    char* filenamebuf =
        (char*)malloc(filenamelength);

    
    cmc_list_item_init(item);

    snprintf(filenamebuf, filenamelength, filename);

    item->filename = filenamebuf;
    item->lineno = lineno;
    item->released = 0;
    item->size = size;
    item->ptr = malloc(size);

    cmc_list_append(item);

    return (item->ptr);
}


void* cmc_realloc_base(
      void* pointer,
      uint32_t size,
      const char* filename,
      uint32_t lineno) {
    struct cmc_memblk_node_t* item =
        cmc_list_find_byptr(pointer);
    if (item == 0) {
        /* not allocate use cmchecker */
        assert(0);
    }
    item->ptr = realloc(item->ptr, size);

    item->filename = filename;
    item->lineno = lineno;
    item->size = size;

    return (item->ptr);
}

/* free memory */
void cmc_free_base(
      void* pointer) {
    struct cmc_memblk_node_t* item =
        cmc_list_find_byptr(pointer);
    if (item) {
        ++item->released;
    } else {
        /* memory allocation not use cmcheck */
    }
    free(item->ptr);
    /* item->ptr = 0; */
}


#ifdef __cplusplus
/* new operator & new array operator */

template<class Type>
void* cmc_new_base(
      const char* filename,
      uint32_t lineno) {
    Type* object_pointer;
    try {
        object_pointer = new Type;
    } catch (std::bad_alloc& ex) {
        throw ex;
    }
    struct cmc_memblk_node_t* item =
        (struct cmc_memblk_node_t*)malloc(
          sizeof(struct cmc_memblk_node_t)
        );

    size_t filenamelength = strlen(filename) + 1;
    char* filenamebuf = 
        reinterpret_cast<char*>(malloc(filenamelength));

#ifdef CMC_RTTI
    const char* typename_ =
        typeid(*object_pointer).name();
    size_t typenamelength = strlen(typename_) + 1;
    char* typenamebuf =
        reinterpret_cast<char*>(malloc(typenamelength));
#endif  /* CMC_RTTI */


    cmc_list_item_init(item);
    snprintf(filenamebuf, filenamelength, filename);

#ifdef CMC_RTTI
    snprintf(typenamebuf, typenamelength, typename_);
#endif  /* CMC_RTTI */

    item->filename = filenamebuf;
    item->lineno = lineno;
    item->released = 0;
    item->size = sizeof(Type);
    item->ptr = object_pointer;

#ifdef CMC_RTTI
    item->type = typenamebuf;
#endif  /* CMC_RTTI */

    cmc_list_append(item);

    return (item->ptr);
}


template<class Type>
void* cmc_new_array_base(
      uint32_t length,
      const char* filename,
      uint32_t lineno) {
    Type* array_pointer;
    try {
        array_pointer = new Type;
    } catch (std::bad_alloc& ex) {
        throw ex;
    }

    struct cmc_memblk_node_t* item =
        (struct cmc_memblk_node_t*)malloc(
          sizeof(struct cmc_memblk_node_t)
        );

    size_t filenamelength = strlen(filename) + 1;
    char* filenamebuf = 
        reinterpret_cast<char*>(malloc(filenamelength));

#ifdef CMC_RTTI
    const char* typename_ =
        typeid(*array_pointer).name();
    size_t typenamelength = strlen(typename_) + 1;
    char* typenamebuf =
        reinterpret_cast<char*>(malloc(typenamelength));
#endif  /* CMC_RTTI */


    cmc_list_item_init(item);

    snprintf(filenamebuf, filenamelength, filename);

#ifdef CMC_RTTI
    snprintf(typenamebuf, typenamelength, typename_);
#endif  /* CMC_RTTI */

    item->filename = filenamebuf;
    item->lineno = lineno;
    item->released = 0;
    item->size = sizeof(Type) * length;
    item->ptr = array_pointer;

#ifdef CMC_RTTI
    item->type = typenamebuf;
#endif  /* CMC_RTTI */

    cmc_list_append(item);

    return (item->ptr);
}


template<class Type>
void cmc_delete_base(Type* pointer) {
    struct cmc_memblk_node_t* item =
        cmc_list_find_byptr(pointer);

    if (item) {
        ++item->released;
    } else {
        /* memory allocation not use cmcheck */
    }
    delete pointer;
}


#ifdef _MSC_VER
/* re enable unsafe warning */
#pragma warning(pop)
#endif  /* _MSC_VER */


template<class Type>
void cmc_delete_array_base(Type* pointer) {
    struct cmc_memblk_node_t* item =
        cmc_list_find_byptr(pointer);

    if (item) {
        ++item->released;
    } else {
        /* memory allocation not use cmcheck */
    }
    delete[] pointer;
}
#endif  /* __cplusplus */





/* list management */

/* init item
** param: item
**      item for append to end
*/
void cmc_list_item_init(
      struct cmc_memblk_node_t* item) {
    if (item == 0) return;

    item->filename = 0;
    item->lineno = 0;
    item->next = 0;
    item->prev = 0;
    item->ptr = 0;
    item->released = 0;
    item->size = 0;
#ifdef CMC_RTTI
    item->type = 0;
#endif  /* CMC_RTTI */
}

/* append item to list end
** param: item
**      item to append
*/
void cmc_list_append(
      struct cmc_memblk_node_t* item) {
    if (cmc_memblk_list_head == 0) {
        cmc_memblk_list_head = item;
    }

    item->next = 0;
    item->prev = cmc_memblk_list_tail;
    if (cmc_memblk_list_tail != 0) {
        cmc_memblk_list_tail->next = item;
    }
    cmc_memblk_list_tail = item;

    ++cmc_memblk_list_size;
}
      
void cmc_list_remove(
      struct cmc_memblk_node_t* item) {
    if (item == 0) return;


    if (item->prev == 0) {
        cmc_memblk_list_head = item->next;
    } else {
        item->prev->next = item->next;
    }
    if (item->next == 0) {
        cmc_memblk_list_tail = item->prev;
    } else {
        item->next->prev = item->prev;
    }

    cmc_list_release_item(item);
    free(item);

    --cmc_memblk_list_size;
}

/* release memory of the item
** param: item
**      item for release
** notice: this function only release the memory
**      occupied by the members of this item is
**      not the release of the item itself
*/
void cmc_list_release_item(
      struct cmc_memblk_node_t* item) {
    if (item->filename) {
        free((void*)item->filename);
        item->filename = 0;
    }

#ifdef CMC_RTTI
    if (item->type) {
        free((void*)item->type);
        item->type = 0;
    }
#endif  /* CMC_RTTI */
}

/* find special item by pointer from list
** param: ptr
**        pointer of item to find
**
** return:
**        if found return the result
**        otherwise return 0
*/
struct cmc_memblk_node_t*
      cmc_list_find_byptr(void* ptr) {
    struct cmc_memblk_node_t* item =
        cmc_memblk_list_head;
    while (item != 0) {
        if (item->released == 1) {
            item = item->next;
            continue;
        }
        /* if found */
        if (item->ptr == ptr) {
            return item;
        }
        item = item->next;
    }
    /* not found */
    return 0;
}

#endif  /* CMCHECK_H_ */
